/*
  Program: Mine Mayhem
  File:    minefield.cc
  Date:    July, 1996 to February,   1997,    version 1.00
	   26 October to 3 December,	      version 2.00
	   8 December to 4 January,  2001,    version 2.10
	   15 January,			      version 2.11
	   18 September to 16 December, 2006, version 2.90

  Everything to do with the minefield.

  Freeware, Copyright 2006 Jason Hood.
*/

#include "minefld.h"
#include "board.h"
#include "options.h"
#include "sound.h"
#include "faces.h"

#include "mainmenu.h"
extern MainMenuWindow* Main;


// Determine the "difficulty" (tiles per mine) of the layout - lower is harder.
double Layout::diff() const
{
  return double(field->area(dim)) / mines;
}


/* -----------------------------   Minefield   ------------------------------ */

Minefield::Minefield() : ht(0)
{
}


/* --------------------------	CreateMinefield   ---------------------------

  Allocate memory for the minefield and pointers to it.

*/

void Minefield::CreateMinefield()
{
  int max = max_cols * max_rows;
  field = new Tile[max];
  for (int t = 0; t < max; ++t)
    field[t].surround = NULL;

  perimeter = new int[2 * max_cols + 2 * (max_rows - 2) + 1];

  grid = new Tile**[max_rows];
  for (int y = 0; y < max_rows; ++y)
    grid[y] = new Tile*[max_cols];

  EveryTile();
}


/* -----------------------------   ~Minefield	-----------------------------

  Free the memory allocated.

*/

Minefield::~Minefield()
{
  for (int y = max_rows; --y >= 0;)
    delete[] grid[y];
  delete[] grid;

  delete[] perimeter;

  for (int t = max_cols * max_rows; --t >= 0;)
    delete[] field[t].surround;
  delete[] field;
}


/* ------------------------------   SetLevel   ------------------------------

  Set the layout according to the Level.  If Level is zero then play the same
  level that was played before.  If Level is invalid then play Beginner.
  If from_ini is true then the custom and random games are taken from the
  initialisation file, rather than selecting a new game.

*/

void Minefield::SetLevel(int Level, int from_ini)
{
  switch (Level)
  {
    case BEGINNER:     layout = Beginner;     break;
    case INTERMEDIATE: layout = Intermediate; break;
    case EXPERT:       layout = Expert;       break;

    case CUSTOM:
      if (from_ini || CustomDlg().Run())
	layout = Custom;
      else
	Level = level;
    break;

    default:
      if (Level < 2 || Level > 10)
      {
	Level  = BEGINNER;
	layout = Beginner;
      }
      else
      {
	if (!from_ini)
	  RandomLayout(Level);
        layout = Random;
      }
    break;
  }
  level = Level;
  EveryTile();
}


/* ------------------------------   Surround   ------------------------------

  Create the list of neighbouring tiles and the solver set mask.

*/

void Minefield::Surround(Tile* t, int num, cCoord ofs[])
{
  int x = t->grid.x, y = t->grid.y;
  if (t->surround == NULL)
    t->surround = new Tile*[num+1];
  Tile** tile = t->surround;
  t->mask = 0;
  for (int o = 0; o < num; ++o)
  {
    int xx = addx(x, ofs[o].x),
	yy = addy(y, ofs[o].y);
    if (xx < 0 || xx >= cols || yy < 0 || yy >= rows || !grid[yy][xx])
      continue;
    *tile++ = grid[yy][xx];
    t->mask |= bit[ofs[o].y+1][ofs[o].x+1];
  }
  *tile = NULL;
}


/* -------------------------------   NewGame   ------------------------------

  Start a new game with level Level.  If Level is 1 reset the previous game.

*/

void Minefield::NewGame(int Level)
{
  StopAllSamples();

  if (Level >= 2)
  {
    SetLevel(Level, false);
    Main->cmGame(true); 		// In case the field can no longer wrap
  }

  mines     =
  mynes     =
  minestogo = layout.mines;
  spaces    = tile_count - mines;
  opened    =
  marked    = 0;
  if (::field == recthexfield)
    distance = rows + ((cols-1) * 2 + ((rows-1) & 1) - (rows-1)) / 2;
  else
    distance = MAX(cols, rows);

  tileclues =
  mineclues = 0;
  ss.clear();

  isStarted   = false;
  isFinished  = false;
  isRestarted = (Level == 1);

  Board->NewGame(GetBoardDimensions(layout.dim), mines, spaces);
  foralltiles
  {
    field[tile].marks  = 0;
    field[tile].hint   = NONE;
    field[tile].status = UNOPENED;
    DisplayTile(&field[tile], UNOPENED);
  }
  Board->Display();
}


/* ----------------------------   RandomLayout	 ----------------------------

  Calculate the dimensions to satisfy the difficulty.  diff is assumed to be
  valid (ie. between 2 and 10).  There will always exist a layout to satisfy
  the difficulty - a dimension will be a multiple of it.

*/

void Minefield::RandomLayout(int diff)
{
  int a;
  do
  {
    RandomDim();
    a = area(Random.dim);
  } while (a % diff != 0);
  Random.mines = a / diff;
}


/* ------------------------------   Progress   ------------------------------

  Return the progress of the game.  For the clear game this is the percentage
  completed; for the cross game it is the ideal distance to the end.

*/

int Minefield::Progress() const
{
  if (Game == CROSS)
    return distance;

  int pc;
  int m = layout.mines;
  int o = tile_count - m;
  switch (Finish)
  {
    case ALL_MARKED: pc = 100 * (m - mines) / m;  break;
    case ALL_OPENED: pc = 100 * (o - spaces) / o; break;
    default:	     pc = 100 * (tile_count - mines - spaces) / tile_count;
  }
  return pc;
}


/* ----------------------------   GameProgress	 ----------------------------

  Return the current state of the game:

    NOT_PLAYING: the field has been drawn, but the game hasn't yet started
    PLAYING:	 the game is in progress
    FINISHED:	 the game has been won or lost

*/

int Minefield::GameProgress() const
{
  return (!isStarted) ? NOT_PLAYING : (!isFinished) ? PLAYING : FINISHED;
}


/* -----------------------------   MineCount   ------------------------------

  Increase or decrease the mine count of the tile and its neighbours.

*/

void Minefield::MineCount(Tile* t, int delta)
{
  on_around (t, t1)
    (*t1)->mines += delta;
}


/* -----------------------------   MarkCount   ------------------------------

  Increase or decrease the mark count of the tile and its neighbours.

*/

void Minefield::MarkCount(Tile* t, int delta)
{
  on_around (t, t1)
    (*t1)->marks += delta;
}


/* -----------------------------   ShowCount   ------------------------------

  Determine the bitmap to display for the tile, according to Counter:

    NORMAL: the mine count
    REMOVE: blank if the marks equals the mine count, mine count otherwise
    UPDATE: the mines left to be marked

*/

int Minefield::ShowCount(Tile* t)
{
  int v = t->mines;
  if (Counter == UPDATE)
    v -= t->marks;
  else if (Counter == REMOVE && v == t->marks)
    v = 0;
  return v + t->status;
}


/* -----------------------------   PlaceMine   ------------------------------

  If the game has not yet started place all the mines on the field, otherwise
  just place one mine.	Return false if the mine(s) couldn't be placed.

*/

bool Minefield::PlaceMine()
{
  int valid[tile_count];
  int cnt = 0;

  // If we're just displacing a mine find the remaining valid locations
  // and pick one at random.
  if (isStarted)
  {
    foralltiles
    {
      if (field[tile].status == UNOPENED && !field[tile].mine &&
	  isUnknown(&field[tile]))
	valid[cnt++] = tile;
    }
    if (cnt == 0)
      return false;
    int pos = valid[random() % cnt];
    field[pos].mine = 1;
    MineCount(&field[pos], 1);
    return true;
  }

  // Create a list of all the valid mine locations.
  if (Game == CROSS || Start == BLANK || Start == PERIMETER)
  {
    // Mark everything as a mine, then exclude locations to ignore.
    foralltiles
      field[tile].mine = 1;
    if (Start == PERIMETER)
    {
      for (int* tile = perimeter; *tile >= 0; ++tile)
	field[*tile].mine = 0;
    }
    else
    {
      on_around (starttile, t)
	(*t)->mine = 0;
      if (Game == CROSS)
	on_around (&field[tile_count-1], t)
	  (*t)->mine = 0;
    }
    foralltiles
      if (field[tile].mine)
	valid[cnt++] = tile;
  }
  else
  {
    foralltiles
      valid[cnt++] = tile;
  }

  return Generate(valid, cnt);
}


/* ------------------------------   MoveMine   ------------------------------

  If there's a mine at t move it somewhere else, update the count and
  return true.	If there's no mine return false.

*/

bool Minefield::MoveMine(Tile* t)
{
  if (!t->mine)
    return false;

  if (PlaceMine())
  {
    MineCount(t, -1);
    t->mine = 0;
  }
  return true;
}


/* -----------------------------   isUnknown   ------------------------------

  Return true if there are no opened (or counted marked) tiles around t.

*/

bool Minefield::isUnknown(Tile* t)
{
  around (t, t1)
    if ((*t1)->status == OPENED || (isMarks && (*t1)->status == MARKED))
      return false;

  return true;
}


/* -----------------------------   AllMarked   ------------------------------

  Return true if any tile around t has all its mines marked.

*/

bool Minefield::AllMarked(Tile* t)
{
  around (t, t1)
    if (((*t1)->status == OPENED || (isMarks && (*t1)->status == MARKED)) &&
	(*t1)->mines == (*t1)->marks)
      return true;

  return false;
}


/* ------------------------------   Convert   -------------------------------

  Convert the mouse co-ordinate to a tile.  Returns false if the co-ordinate
  is not on the grid, otherwise true.  The cross game will also return false
  if the tile hasn't been determined by the solver.

*/

bool Minefield::Convert(int x, int y, Tile*& t)
{
  if (x < 0 || y < 0)
    return false;
  Coord b = GetBoardDimensions(layout.dim);
  if (x >= b.x || y >= b.y)
    return false;
  Convert(x, y);
  if (x < 0 || x >= cols || y < 0 || y >= rows || !grid[y][x])
    return false;

  t = grid[y][x];

  if (Game == CROSS && GameProgress() == PLAYING)
    return (t->status == OPENED || t->hint != NONE);

  return true;
}


/* -----------------------------   LeftButton	-----------------------------

  Open or clear-around a tile.	If the tile is blank, or automatic opening or
  clearing is on and the tile has all its mines marked, open all surrounding
  tiles.  Start timing if it's the first press.

*/

int Minefield::LeftButton(Tile* t)
{
  if (isFinished)
    return 0;

  ++opened;

  if (t->status != UNOPENED)
  {
    if (Game == CROSS && !isAutoClear && !isAutoOpen)
      return 0;
    return ClearAround(t, true);
  }

  if (!isStarted)
  {
    if (!isRestarted)
    {
      if (Game == CROSS)
	t = &field[0];
      starttile = t;
      if (!PlaceMine())
      {
	--opened;
	return 0;
      }
    }
    else if (Game == CROSS || Start == BLANK)
      t = starttile;
    isStarted = true;
    hintsused = Hints;
    Board->StartTiming();

    if (Game == CLEAR && Start == PERIMETER)
    {
      for (int* tile = perimeter; *tile >= 0; ++tile)
      {
	if (field[*tile].status == UNOPENED)
	{
	  Open(&field[*tile]);
	  if (field[*tile].mines == 0 && !isFinished)
	    Clear(&field[*tile]);
	  if (isFinished)
	    break;
	}
      }
      return 1;
    }
  }

  if (t->mine && Game == CLEAR &&
      (Start == DISPLACE || (Start == BLANK && !isLogical)) && isUnknown(t))
    MoveMine(t);

  Open(t);
  if (!isFinished &&
      ((t->mines == 0 && (Game == CLEAR || isAutoClear || isAutoOpen)) ||
       (t->mines == t->marks && (isAutoClear || isAutoOpen))))
    Clear(t);

  return (t->mine == 0);
}


/* ----------------------------   RightButton	-----------------------------

  Mark, unmark or mark-around a tile.  You cannot mark more tiles than there
  are mines (since this renders the all-marked finishing condition meaning-
  less), nor can you add a mark to a tile that has all its mines identified.
  Prevent marking an unknown tile if the hints are being displayed.  If the
  marks counter is in effect, mark-around or unmark, as appropriate.

*/

int Minefield::RightButton(Tile* t)
{
  if (!isStarted)
    return 0;

  ++marked;

  switch (t->status)
  {
    case OPENED: return MarkAround(t, true);

    case UNOPENED:
      if (mines == 0 ||
	  (Hints == WHERE && t->hint == NONE) ||
	  (Hints == WHAT && t->hint != HINTMARK) ||
	  AllMarked(t))
	return 0;
    break;

    case MARKED:
      if (isMarks && MarkAround(t))
	return 1;
    break;
  }

  Mark(t);
  if (!isFinished && isMarks && t->status == MARKED && t->mines == t->marks &&
      isAutoClear && !isAutoOpen)
    Clear(t);

  return (t->mine) ? (t->status == MARKED ? 1 : -1) : 0;
}


/* --------------------------------   Open   --------------------------------

  Open the tile (assumed unopened and unmarked) and test for game over.

*/

void Minefield::Open(Tile* t)
{
  t->status = OPENED;
  if (t->hint)
  {
    --tileclues;
    t->hint = NONE;
  }

  if (t->mine)
  {
    DisplayTile(t, DEADMINE);
    GameOver(true);
    return;
  }

  if (isSound && OpenSnd)
    PlaySampleFVP(open_snd, open_snd->frequency, open_snd->volume,
		  PanLevel(t->loc));

  DisplayTile(t, ShowCount(t));

  --spaces;
  if (GameOver())
    return;

  if (Game == CROSS)
  {
    int d;
    if (::field == recthexfield)
    {
      d = rows-1 - t->grid.y;
      d += ((cols-1) * 2 + ((rows-1) & 1)
	    - (t->grid.x * 2 + (t->grid.y & 1) + d)) / 2;
    }
    else
      d = MAX(cols-1 - t->grid.x, rows-1 - t->grid.y);
    if (d < distance)
      distance = d;
  }

  if (t->mines != 0 || (Game == CROSS && !isAutoClear && !isAutoOpen))
    std.add(t);

  if (isAutoMark)
  {
    on_around (t, t1)
    {
      if ((*t1)->status == OPENED || (isMarks && (*t1)->status == MARKED))
      {
	MarkAround(*t1);
	if (isFinished)
	  break;
      }
    }
  }
}


/* -------------------------------   Clear   --------------------------------

  Clear around the selected tile.  This is used to automatically open tiles
  that have no mines around them and to clear around a tile that has all
  its mines marked.

*/

bool Minefield::Clear(Tile* t)
{
  bool rc = false;

  around (t, t1)
  {
    if ((*t1)->status == UNOPENED)
    {
      rc = true;
      Open(*t1);
      if (isFinished)
	break;
      if (((*t1)->mines == 0 && (Game == CLEAR || isAutoClear || isAutoOpen)) ||
	  ((*t1)->mines == (*t1)->marks && isAutoOpen))
      {
	Clear(*t1);
	if (isFinished)
	  break;
      }
    }
  }

  return rc;
}


/* ----------------------------   ClearAround   -----------------------------

  If the number of marked mines around t equals the value of t then
  open all the surrounding tiles.  If it doesn't then highlight the unopened
  tiles (provided doHilite is true).

*/

bool Minefield::ClearAround(Tile* t, bool doHilite)
{
  if (t->status == UNOPENED || (t->status == MARKED && !isMarks))
    return false;

  if (t->mines == t->marks)
    return Clear(t);

  if (doHilite)
    Highlight(t, MINE);
  return false;
}


/* ----------------------------   MarkAround   ------------------------------

  If the number of unopened and marked mines around t equals the value of t
  then mark all the surrounding tiles and return true; if it doesn't then
  possibly highlight the unopened tiles and return false.

*/

bool Minefield::MarkAround(Tile* t, bool doHilite)
{
  if (t->mines == t->marks)
    return false;

  int m = t->marks;
  around (t, t1)
  {
    if ((*t1)->status == UNOPENED)
    {
      if (AllMarked(*t1))
	return false;
      ++m;
    }
  }
  if (t->mines == m)
  {
    around (t, t1)
    {
      if ((*t1)->status == UNOPENED)
      {
	// Auto-mark will see it before the solver.
	if ((*t1)->hint == NONE && (*t1)->mine)
	{
	  (*t1)->hint = HINTMARK;
	  ++tileclues;
	  ++mineclues;
	}
	Mark(*t1);
	if (isFinished)
	  break;
      }
    }
    return true;
  }

  if (doHilite)
    Highlight(t, OPENED);
  return false;
}


/* -----------------------------   Highlight   ------------------------------

  Highlight unknown tiles for clear-around and mark-around.  The highlight is
  removed when the mouse button is released.

*/

void Minefield::Highlight(Tile* t, int what)
{
  int v = what;

  around (t, t1)
  {
    if ((*t1)->status == UNOPENED)
    {
      if (what == UNOPENED && Hints > CLUES)
	v = ((*t1)->hint == NONE) ? UNOPENED :
	    (Hints == WHERE)	  ? HINT : (*t1)->hint;
      DisplayTile(*t1, v);
    }
  }
  ht = t;
}

void Minefield::UnHighlight()
{
  if (ht)
  {
    Highlight(ht, UNOPENED);
    ht = 0;
  }
}


/* --------------------------------   Mark   --------------------------------

  Mark an unopened tile with a flag or remove a mark.  Update the flag count
  and test for game over (Finish & ALL_MARKED).

*/

void Minefield::Mark(Tile* t)
{
  int v;

  switch (t->status)
  {
    case UNOPENED:
      --mines;
      mynes -= t->mine;
      MarkCount(t, 1);
      v = t->status = MARKED;
      if (isMarks)
	v = ShowCount(t);
      t->hint = (t->hint == NONE)     ? HINTMARKU :
		(t->hint == HINTOPEN) ? HINTMARKX : NONE;
      if (t->hint == NONE)
      {
	--tileclues;
	--mineclues;
	--minestogo;
	std.add(t);
      }
    break;

    case MARKED:
      ++mines;
      mynes += t->mine;
      MarkCount(t, -1);
      v = t->status = UNOPENED;
      if (t->hint == NONE)
      {
	// Unmarking a known mine requires resetting all the hints
	// and solving everything again (it really only requires
	// resetting everything connected to the mark, but it's
	// easier just to do everything).
	++minestogo;
	tileclues = mineclues = 0;
	ss.clear();
	foralltiles
	{
	  Tile& t = field[tile];
	  if (t.hint)
	  {
	    t.hint = (t.status == MARKED) ? HINTMARKU : NONE;
	    if (Hints > CLUES)
	      DisplayTile(&t, t.status);
	  }
	  else if (t.status != UNOPENED && (t.mines != 0
		   || (Game == CROSS && !isAutoClear && !isAutoOpen)))
	    std.add(&t);
	}
      }
      else if (t->hint == HINTMARKX)
      {
	t->hint = HINTOPEN;
	if (Hints > CLUES)
	  v = (Hints == WHERE) ? HINT : HINTOPEN;
      }
      else
	t->hint = NONE;
    break;

    default: return;
  }

  if (isSound && MarkSnd)
    PlaySampleFVP(mark_snd, mark_snd->frequency, mark_snd->volume,
		  PanLevel(t->loc));

  DisplayTile(t, v);
  if (Counter != NORMAL)
    around (t, t1)
      if ((*t1)->status == OPENED || (isMarks && (*t1)->status == MARKED))
	DisplayTile(*t1, ShowCount(*t1));

  if (mynes == 0)
    if (GameOver())
      return;

  if (t->status == MARKED)
  {
    if (isAutoMark && isMarks)
      MarkAround(t);
    if (isAutoOpen && !isFinished)
    {
      on_around (t, t1)
      {
	ClearAround(*t1);
	if (isFinished)
	  break;
      }
    }
  }
}


/* ------------------------------   GameOver   ------------------------------

  The game is over when a mine is opened (dead == true); when all the non-mine
  tiles have been opened (Finish & ALL_OPENED), or all the mine tiles have been
  marked (Finish & ALL_MARKED), or both; or the field has been crossed.
  If the game was lost then all the mines and incorrect marks are displayed.
  If the clear game was won then all the mines are marked and all the tiles are
  opened (but using hint can still show what you didn't do).

*/

bool Minefield::GameOver(bool dead)
{
  if (!dead)
  {
    if (Game == CLEAR)
    {
      if (!((spaces == 0 && (Finish & ALL_OPENED)) ||
	    (mynes  == 0 && (Finish & ALL_MARKED)) ||
	    (spaces == 0 && mynes == 0 && Finish == 0)))
	return false;
    }
    else
    {
      if (field[tile_count-1].status != OPENED)
	return false;
    }
  }
  score = Board->GameOver(dead);
  isFinished = dead ? LOST : WON;

  if (dead)
  {
    if (isSound && DieSnd)
      PlaySample(die_snd);
    foralltiles
    {
      if (field[tile].mine)
      {
	if (field[tile].status == UNOPENED)
	  DisplayTile(&field[tile], MINE);
      }
      else if (field[tile].status == MARKED)
      {
	DisplayTile(&field[tile], WRONGMINE);
	++mines;
      }
    }
  }
  else
  {
    if (isSound && WinSnd)
      PlaySample(win_snd);
    if (Game == CLEAR)
    {
      int hints = Hints;
      Hints = NONE;
      foralltiles
      {
	int t = (field[tile].mine) ? MARKED : field[tile].mines;
	DisplayTile(&field[tile], t);
      }
      Hints = hints;
      Board->DisplayClues(-1);
    }
    else if (Hints)
      Board->DisplayClues(tileclues);
  }

  return true;
}


/* ------------------------------   Find3BV   -------------------------------

  Determine Bechtel's Board Benchmark Value.  If opened is true only
  determine the 3BV of what has been opened, rather than the entire field.

*/

int Minefield::Find3BV(bool opened)
{
  int bbbv = 0;

  int status[tile_count];
  foralltiles
    status[tile] = field[tile].status;

  if (opened)
  {
    // Invert the opened status, so we can ignore what is not opened and
    // concentrate on what has been opened.
    foralltiles
      field[tile].status = (field[tile].status == OPENED) ? UNOPENED : OPENED;
  }
  else
  {
    foralltiles
      field[tile].status = UNOPENED;
  }
  std.head = 0;

  // Count the perimeter as one click.
  if (Start == PERIMETER)
  {
    ++bbbv;
    for (int* tile = perimeter; *tile >= 0; ++tile)
    {
      Tile* t = &field[*tile];
      if (t->status == UNOPENED)
      {
	t->status = OPENED;
	if (t->mines == 0)
	{
	  do
	  {
	    around (t, t1)
	    {
	      if ((*t1)->status == UNOPENED)
	      {
		(*t1)->status = OPENED;
		if ((*t1)->mines == 0)
		  std.add(*t1);
	      }
	    }
	  } while ((t = std.extract()));
	}
      }
    }
  }

  // Find all the blank regions.
  foralltiles
  {
    if (field[tile].mines == 0 && field[tile].status == UNOPENED)
    {
      ++bbbv;
      Tile* t = &field[tile];
      t->status = OPENED;
      do
      {
	around (t, t1)
	{
	  if ((*t1)->status == UNOPENED)
	  {
	    (*t1)->status = OPENED;
	    if ((*t1)->mines == 0)
	      std.add(*t1);
	  }
	}
      } while ((t = std.extract()));
    }
  }

  // Now find everything else.
  foralltiles
    if (field[tile].status == UNOPENED && !field[tile].mine)
      ++bbbv;

  foralltiles
    field[tile].status = status[tile];

  return bbbv;
}


/* -------------------------------   Pause   --------------------------------

  Blank out the minefield.

*/

void Minefield::Pause()
{
  foralltiles
    DisplayTile(&field[tile], OPENED);
}


/* ----------------------------   DisplayTile	-----------------------------

  Display the tile using the appropriate bitmaps.

*/

void Minefield::DisplayTile(Tile* t, int which, int w, int h, ViewBuffer* bmp[])
{
#define BoardDisplayBmp(buf) \
  Board->DisplayTile(t->loc.x, t->loc.y, w, h, bmp[buf])

#define BoardDisplaySqr(buf) \
  Board->DisplayTile(t->loc.x + (w+1 - SQUAREW) / 2, \
		     t->loc.y + (h+1 - SQUAREH) / 2, \
		     SQUAREW-1, SQUAREH-1, Square[buf])

#define BoardDisplayOvr(buf) \
  Board->DisplayTile(t->loc.x + (w+1 - OTILEW) / 2, \
		     t->loc.y + (h+1 - OTILEH) / 2, \
		     OTILEW-1, OTILEH-1, Overlay[buf])

  if (which == UNOPENED)
  {
    BoardDisplayBmp(bufUNOPENED);
  }
  else if (which == OPENED)
  {
    BoardDisplayBmp(bufOPENED);
  }
  else if (which < MINE)
  {
    BoardDisplayBmp(bufOPENED);
    BoardDisplayOvr(which);
  }
  else if (which == MINE || which == WRONGMINE || which == DEADMINE)
  {
    BoardDisplayBmp(which == DEADMINE ? bufDEADMINE : bufOPENED);
    BoardDisplaySqr(bufMINE);
    if (which == WRONGMINE)
      BoardDisplaySqr(bufWRONGMINE);
  }
  else if (which >= MARKED)
  {
    if (isMarks && which != MARKED)
    {
      BoardDisplayBmp((Hints <= CLUES || t->hint == NONE) ? bufMARKBG :
		      t->hint - HINTMARKX + bufMARKXBG);
      BoardDisplayOvr(which - MARKED);
    }
    else
    {
      BoardDisplayBmp(bufUNOPENED);
      BoardDisplayOvr((Hints <= CLUES || t->hint == NONE) ? bufMARKED :
		      t->hint - HINTMARKX + bufHINTMARKX);
    }
  }
  else // HINT || HINTMARK || HINTOPEN
  {
    BoardDisplayBmp(bufUNOPENED);
    BoardDisplayOvr(which - HINT + bufHINT);
  }
}


/* ------------------------------   PanLevel   ------------------------------

  Return the panning associated with the tile being opened or marked, in
  relation to the minefield, not the screen.

*/

int Minefield::PanLevel(cCoord& c) const
{
  return (65536 * ((SwapStereo) ? c.x : (pan_wid - c.x)) / pan_wid);
}


/* ------------------------------   Validate   ------------------------------

  Validate the number of mines given an already validated dimension.

*/

void Minefield::Validate(Layout& layout)
{
  if (layout.mines < 1)
    layout.mines = 1;
  else
  {
    int m = area(Coord(layout.dim.x, layout.dim.y)) / 2;
    if (layout.mines > m)
      layout.mines = m;
  }
}


/* ------------------------------   ReadIni   -------------------------------

  Read the custom and random games, and level, from the initialisation file,
  verify that those games are valid, and load the times.
  Note that the custom and random layouts have to be initialised before the
  call to SetLevel().

*/

void Minefield::ReadIni(IniFile& ini, const char* section, const char* file)
{
  Custom.dim.x = ini.GetInt(section, "Custom.Width");
  Custom.dim.y = ini.GetInt(section, "Custom.Height");
  Custom.mines = ini.GetInt(section, "Custom.Mines");
  Validate(Custom);

  Random.dim.x = ini.GetInt(section, "Random.Width");
  Random.dim.y = ini.GetInt(section, "Random.Height");
  Random.mines = ini.GetInt(section, "Random.Mines");
  Validate(Random);

  SetLevel(ini.GetInt(section, "Level", BEGINNER));

  ::field = this;
  times.Load(file, CustomPopup);
}


/* ------------------------------   WriteIni   ------------------------------

  Write the level and custom and random games to the initialisation file, and
  save the times.

*/

void Minefield::WriteIni(IniFile& ini, const char* section, const char* file)
{
  ini.WriteInt(section, "Level",         level);

  ini.WriteInt(section, "Custom.Width",  Custom.dim.x);
  ini.WriteInt(section, "Custom.Height", Custom.dim.y);
  ini.WriteInt(section, "Custom.Mines",  Custom.mines);

  ini.WriteInt(section, "Random.Width",  Random.dim.x);
  ini.WriteInt(section, "Random.Height", Random.dim.y);
  ini.WriteInt(section, "Random.Mines",  Random.mines);

  times.Save(file);
}
